package main

import (
	"bufio"
	"database/sql"
	"fmt"
	"log"
	"os"
	"strings"

	_ "gitee.com/chunanyong/dm" // 达梦数据库驱动
)

// 数据库连接配置
type DBConfig struct {
	IP       string
	Port     string
	Instance string
	Username string
	Password string
}

// 应用总配置
type AppConfig struct {
	SrcConfig DBConfig
	DstConfig DBConfig
	Tables    []string
}

// 表结构信息
type TableStructure struct {
	Columns []ColumnInfo
	Indexes []IndexInfo
}

// 字段信息
type ColumnInfo struct {
	Name string
	Type string
}

// 索引信息
type IndexInfo struct {
	Name    string
	Columns string
}

// 对比结果
type CompareResult struct {
	StructureDiff []string
	DataDiff      []string
}

// 从 db.conf 读取配置
func loadConfig(filePath string) (AppConfig, error) {
	var cfg AppConfig

	file, err := os.Open(filePath)
	if err != nil {
		return cfg, fmt.Errorf("打开配置文件失败: %v", err)
	}
	defer file.Close()

	configMap := make(map[string]string)
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}
		parts := strings.SplitN(line, "=", 2)
		if len(parts) != 2 {
			continue
		}
		key := strings.TrimSpace(parts[0])
		val := strings.TrimSpace(parts[1])
		configMap[key] = val
	}

	if err := scanner.Err(); err != nil {
		return cfg, fmt.Errorf("读取配置文件失败: %v", err)
	}

	cfg.SrcConfig = DBConfig{
		IP:       configMap["SRC_IP"],
		Port:     configMap["SRC_PORT"],
		Instance: configMap["SRC_INSTANCE"],
		Username: configMap["SRC_USERNAME"],
		Password: configMap["SRC_PASSWORD"],
	}
	cfg.DstConfig = DBConfig{
		IP:       configMap["DST_IP"],
		Port:     configMap["DST_PORT"],
		Instance: configMap["DST_INSTANCE"],
		Username: configMap["DST_USERNAME"],
		Password: configMap["DST_PASSWORD"],
	}
	cfg.Tables = strings.Split(configMap["TABLES"], ",")

	return cfg, nil
}

// 连接数据库
func connectDB(config DBConfig) (*sql.DB, error) {
	dsn := fmt.Sprintf("dm://%s:%s@%s:%s/%s",
		config.Username, config.Password, config.IP, config.Port, config.Instance)
	return sql.Open("dm", dsn)
}

// 获取表结构
func getTableStructure(db *sql.DB, tableName string) (TableStructure, error) {
	var structure TableStructure

	// 获取字段信息
	columnsQuery := `
		SELECT COLUMN_NAME, DATA_TYPE
		FROM USER_TAB_COLUMNS
		WHERE TABLE_NAME = ?
		ORDER BY COLUMN_NAME`
	rows, err := db.Query(columnsQuery, tableName)
	if err != nil {
		return structure, fmt.Errorf("query columns failed: %v", err)
	}
	defer rows.Close()

	for rows.Next() {
		var col ColumnInfo
		if err := rows.Scan(&col.Name, &col.Type); err != nil {
			return structure, fmt.Errorf("scan column failed: %v", err)
		}
		structure.Columns = append(structure.Columns, col)
	}

	// 获取索引信息
	indexesQuery := `
		SELECT I.INDEX_NAME, C.COLUMN_NAME
		FROM USER_INDEXES I
		JOIN USER_IND_COLUMNS C ON I.INDEX_NAME = C.INDEX_NAME AND I.TABLE_NAME = C.TABLE_NAME
		WHERE I.TABLE_NAME = ?
		ORDER BY I.INDEX_NAME, C.COLUMN_NAME`
	rows, err = db.Query(indexesQuery, tableName)
	if err != nil {
		return structure, fmt.Errorf("query indexes failed: %v", err)
	}
	defer rows.Close()

	indexMap := make(map[string][]string)
	for rows.Next() {
		var indexName, columnName string
		if err := rows.Scan(&indexName, &columnName); err != nil {
			return structure, fmt.Errorf("scan index failed: %v", err)
		}
		indexMap[indexName] = append(indexMap[indexName], columnName)
	}
	for indexName, columns := range indexMap {
		structure.Indexes = append(structure.Indexes, IndexInfo{
			Name:    indexName,
			Columns: strings.Join(columns, ","),
		})
	}

	return structure, nil
}

// 比较表结构
func compareStructures(src, dst TableStructure, tableName string) []string {
	var diffs []string

	// 比较字段
	srcCols := make(map[string]string)
	for _, col := range src.Columns {
		srcCols[col.Name] = col.Type
	}
	dstCols := make(map[string]string)
	for _, col := range dst.Columns {
		dstCols[col.Name] = col.Type
	}

	for name, typ := range srcCols {
		if dstType, exists := dstCols[name]; !exists {
			diffs = append(diffs, fmt.Sprintf("Column %s exists in source but not in target", name))
		} else if typ != dstType {
			diffs = append(diffs, fmt.Sprintf("Column %s type mismatch: source=%s, target=%s", name, typ, dstType))
		}
	}
	for name := range dstCols {
		if _, exists := srcCols[name]; !exists {
			diffs = append(diffs, fmt.Sprintf("Column %s exists in target but not in source", name))
		}
	}

	// 比较索引
	srcIndexes := make(map[string]string)
	for _, idx := range src.Indexes {
		srcIndexes[idx.Name] = idx.Columns
	}
	dstIndexes := make(map[string]string)
	for _, idx := range dst.Indexes {
		dstIndexes[idx.Name] = idx.Columns
	}

	for name, cols := range srcIndexes {
		if dstCols, exists := dstIndexes[name]; !exists {
			diffs = append(diffs, fmt.Sprintf("Index %s exists in source but not in target", name))
		} else if cols != dstCols {
			diffs = append(diffs, fmt.Sprintf("Index %s columns mismatch: source=%s, target=%s", name, cols, dstCols))
		}
	}
	for name := range dstIndexes {
		if _, exists := srcIndexes[name]; !exists {
			diffs = append(diffs, fmt.Sprintf("Index %s exists in target but not in source", name))
		}
	}

	return diffs
}

// 比较表数据
func compareData(srcDB, dstDB *sql.DB, tableName string) ([]string, error) {
	var diffs []string

	query := fmt.Sprintf("SELECT * FROM %s ORDER BY 1", tableName) // 假设第一列是主键
	srcRows, err := srcDB.Query(query)
	if err != nil {
		return nil, fmt.Errorf("query source data failed: %v", err)
	}
	defer srcRows.Close()

	dstRows, err := dstDB.Query(query)
	if err != nil {
		return nil, fmt.Errorf("query target data failed: %v", err)
	}
	defer dstRows.Close()

	columns, err := srcRows.Columns()
	if err != nil {
		return nil, fmt.Errorf("get source columns failed: %v", err)
	}

	srcValues := make([]interface{}, len(columns))
	srcPointers := make([]interface{}, len(columns))
	for i := range srcValues {
		srcPointers[i] = &srcValues[i]
	}
	dstValues := make([]interface{}, len(columns))
	dstPointers := make([]interface{}, len(columns))
	for i := range dstValues {
		dstPointers[i] = &dstValues[i]
	}

	rowNum := 0
	for srcRows.Next() {
		rowNum++
		if !dstRows.Next() {
			diffs = append(diffs, fmt.Sprintf("Row %d exists in source but not in target", rowNum))
			continue
		}

		if err := srcRows.Scan(srcPointers...); err != nil {
			return nil, fmt.Errorf("scan source row %d failed: %v", rowNum, err)
		}
		if err := dstRows.Scan(dstPointers...); err != nil {
			return nil, fmt.Errorf("scan target row %d failed: %v", rowNum, err)
		}

		for i, col := range columns {
			if fmt.Sprintf("%v", srcValues[i]) != fmt.Sprintf("%v", dstValues[i]) {
				diffs = append(diffs, fmt.Sprintf("Row %d, column %s: source=%v, target=%v",
					rowNum, col, srcValues[i], dstValues[i]))
			}
		}
	}
	for dstRows.Next() {
		rowNum++
		diffs = append(diffs, fmt.Sprintf("Row %d exists in target but not in source", rowNum))
	}

	return diffs, nil
}

// 比较单个表
func compareTable(srcDB, dstDB *sql.DB, tableName string) (CompareResult, error) {
	var result CompareResult

	srcStructure, err := getTableStructure(srcDB, tableName)
	if err != nil {
		return result, fmt.Errorf("get source table structure failed: %v", err)
	}
	dstStructure, err := getTableStructure(dstDB, tableName)
	if err != nil {
		return result, fmt.Errorf("get target table structure failed: %v", err)
	}
	result.StructureDiff = compareStructures(srcStructure, dstStructure, tableName)

	result.DataDiff, err = compareData(srcDB, dstDB, tableName)
	if err != nil {
		return result, fmt.Errorf("compare data failed: %v", err)
	}

	return result, nil
}

func main() {
	// 读取配置
	cfg, err := loadConfig("db.conf")
	if err != nil {
		log.Fatalf("加载配置失败: %v", err)
	}

	// 连接源端
	srcDB, err := connectDB(cfg.SrcConfig)
	if err != nil {
		log.Fatalf("连接源端数据库失败: %v", err)
	}
	defer srcDB.Close()
	if err := srcDB.Ping(); err != nil {
		log.Fatalf("源端数据库 Ping 失败: %v", err)
	}

	// 连接目标端
	dstDB, err := connectDB(cfg.DstConfig)
	if err != nil {
		log.Fatalf("连接目标端数据库失败: %v", err)
	}
	defer dstDB.Close()
	if err := dstDB.Ping(); err != nil {
		log.Fatalf("目标端数据库 Ping 失败: %v", err)
	}

	// 循环比较表
	for _, table := range cfg.Tables {
		table = strings.TrimSpace(table)
		if table == "" {
			continue
		}
		fmt.Printf("\nComparing table: %s\n", table)
		result, err := compareTable(srcDB, dstDB, table)
		if err != nil {
			log.Printf("Failed to compare table %s: %v", table, err)
			continue
		}

		if len(result.StructureDiff) > 0 {
			fmt.Println("Structure differences:")
			for _, diff := range result.StructureDiff {
				fmt.Printf("  - %s\n", diff)
			}
		} else {
			fmt.Println("No structure differences found.")
		}

		if len(result.DataDiff) > 0 {
			fmt.Println("Data differences:")
			for _, diff := range result.DataDiff {
				fmt.Printf("  - %s\n", diff)
			}
		} else {
			fmt.Println("No data differences found.")
		}
	}
}
